//测试命令： php php/DocGenerator.php --task=1
package controllers

import (
    "bytes"
    "errors"
    "regexp"
    "fmt"
    "log"
    "net/http"
    "context"
    "os/exec"
    "time"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
    "gouniapp/configs"
    "net/url"
    "strings"
    "path/filepath"
)

type WordRequest struct {
	ID          uint        `json:"id"`         //项目id
	Overwrite   string      `json:"overwrite"`  //是否覆盖
	Field       string      `json:"field"`      //word字段
	Wjm         string      `json:"wjm"`        //文件名字段
	IDs         []uint      `json:"ids"`        //行id 
}
    
// 批量生成Word文档
func Plscword(c *gin.Context) {
    var query WordRequest
    if err := c.ShouldBindJSON(&query); err != nil {
        JsonResponse(c, http.StatusBadRequest, "请求参数无效", nil)
        return
    }
    if query.Field == "" {
        JsonResponse(c, http.StatusInternalServerError, "word字段不存在", nil)
        return
    }
    if query.Wjm == "" {
        JsonResponse(c, http.StatusInternalServerError, "文件名字段不存在", nil)
        return
    }
    // 获取项目属性
    bgsx, err := GetFormSchema(query.ID)
    if err != nil {
        JsonResponse(c, http.StatusInternalServerError, "项目不存在", err.Error())
        return
    }
    if bgsx.Wordmb == "" {
        JsonResponse(c, http.StatusInternalServerError, "模板不存在", nil)
        return
    }
    // 检查模板文件是否存在，如：https://zp.kzhgzs.cn/uploads/wordmb/aa.docx
    wordfile := sanitizePath(bgsx.Wordmb)
    if wordfile == "" || !configs.PathExists(wordfile) {
        JsonResponse(c, http.StatusInternalServerError, "模板文件无法读取", nil)
        return
    }
    xmid := bgsx.ID // 项目ID
    // 检查任务是否已在进行
    var existingTask configs.DocGenTask
    if err := configs.DB.Where("xmid = ? AND status IN ('pending', 'processing')", xmid).First(&existingTask).Error; err == nil {
        JsonResponse(c, http.StatusConflict, "存在未完成的任务", nil)
        return
    }
    // 检查是否有数据要生成
    var total int64
    cx := configs.DB.Table(bgsx.TableName)
    overwrite := "是"
    if query.Overwrite == "否" {
        overwrite = "否"
        cx.Where(query.Field + " IS NULL").Or(query.Field + " = ''")
    }
    if len(query.IDs) > 0 {
		cx.Where("id IN ?", query.IDs)
	}
    if err := cx.Count(&total).Error; err != nil {
        JsonResponse(c, http.StatusInternalServerError, "获取总数失败", nil)
        return
    }
    if total == 0 {
        JsonResponse(c, http.StatusInternalServerError, "无数据要生成", nil)
        return
    }
    //创建新任务
    newTask := configs.DocGenTask{
        Xmid:       xmid,
        Progress:   0,
        Overwrite:  overwrite,
        Wjm:        query.Wjm,
        Field:      query.Field,
        Status:     "pending",
    }
    if err := configs.DB.Create(&newTask).Error; err != nil {
        JsonResponse(c, http.StatusInternalServerError, "任务创建失败", err.Error())
        return
    }
    // 异步执行任务（带超时控制）
    ctx, _ := context.WithTimeout(context.Background(), 1*time.Hour)
    //defer cancel()
    // 启动异步任务
    go func(taskID uint) {
        callPHPScript(ctx, taskID)
    }(newTask.ID)
    JsonResponse(c, http.StatusOK, "任务创建成功", gin.H{
        "taskId": newTask.ID,
    })
}

// 安全路径检查
func sanitizePath(wordfile string) string {
    parsedURL, err := url.Parse(wordfile)
    if err != nil {
        return ""
    }
    // 去除协议和域名部分
    rawPath := strings.TrimPrefix(parsedURL.Path, "/")
    // 转换路径分隔符
    cleanPath := filepath.ToSlash(rawPath)
    // 限制允许的路径范围
    allowedPrefix := "uploads/wordmb/"
    if !strings.HasPrefix(cleanPath, allowedPrefix) {
        return ""
    }
    // 清理路径中的非法字符
    cleaned := filepath.Clean(cleanPath)
    // 防止路径穿越攻击
    if strings.Contains(cleaned, "../") || strings.Contains(cleaned, "..\\") {
        return ""
    }
    return cleaned
}

// 执行php脚本
func callPHPScript(ctx context.Context, taskID uint) {
    // 创建不绑定 Context 的命令（避免提前取消）
    cmd := exec.Command("php", "php/DocGenerator.php", fmt.Sprintf("--task=%d", taskID))
    // 捕获输出流
    var stdoutBuf, stderrBuf bytes.Buffer
    cmd.Stdout = &stdoutBuf
    cmd.Stderr = &stderrBuf
    // 启动进程
    if err := cmd.Start(); err != nil {
        log.Printf("启动 PHP 进程失败: %v", err)
        return
    }
    // 使用 Channel 监听完成和 Context 取消事件
    done := make(chan error, 1)
    go func() { done <- cmd.Wait() }()
    select {
    case <-ctx.Done():
        // Context 被取消时终止进程
        log.Printf("主动终止任务: %v", ctx.Err())
        cmd.Process.Kill()
        updateTaskStatusword(taskID, fmt.Sprintf("任务被取消: %v", ctx.Err()))
    case err := <-done:
        // 正常执行完成
        stdoutStr := stdoutBuf.String()
        stderrStr := stderrBuf.String()
        log.Printf("PHP RAW OUTPUT:\nstdout: %q\nstderr: %q", stdoutStr, stderrStr)
        errorMsg := ""
        if stderrStr != "" {
            re := regexp.MustCompile(`\[ERROR\] (.+?)(\n|$)`)
            if matches := re.FindStringSubmatch(stderrStr); len(matches) >= 2 {
                errorMsg = strings.TrimSpace(matches[1])
            } else {
                // 兜底：取第一行
                errorMsg = strings.Split(stderrStr, "\n")[0]
            }
            errorMsg = "[STDERR] " + stderrStr
        }
        if err != nil {
            errorMsg += fmt.Sprintf(" (Exit Code: %v)", err)
        }
        updateTaskStatusword(taskID, errorMsg)
    }
}

func updateTaskStatusword(taskID uint, errorMsg string) {
    task := configs.DocGenTask{ID: taskID}
    updates := map[string]interface{}{
        "status": "completed",
    }
    if errorMsg != "" {
        updates["status"] = "failed"
        updates["error_message"] = errorMsg
    }
    if err := configs.DB.Model(&task).Updates(updates).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
        log.Printf("更新任务状态失败: %v", err)
    }
}

// 查询任务状态
func GetTaskStatus(c *gin.Context) {
    taskid := c.Param("taskid")
    var task configs.DocGenTask
    result := configs.DB.Where("id = ?", taskid).Order("created_at DESC").First(&task)
    if result.Error != nil {
        JsonResponse(c, http.StatusNotFound, "暂无任务记录", nil)
        return
    }
    JsonResponse(c, http.StatusOK, "成功", gin.H{
        "taskId":   task.ID,
        "progress": task.Progress,
        "status":   task.Status,
        "error":    task.ErrorMsg,
    })
}